﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
using Miris.HumanMilkAnalyser.Repository.Interface;
using Miris.HumanMilkAnalyser.Service.Interface;

namespace Miris.HumanMilkAnalyser.Service
{
    /// <summary>
    /// An instance of ServiceBase against the specific DataModel type and provide standard CRUD operation only.
    /// </summary>
    /// <typeparam name="T">The specific DataModel type.</typeparam>
    public class ServiceBase<T> : IService<T> where T : class
    {
        protected IRepositoryFactory RepositoryFactory;
        protected IDataValidator<T> EntityValidator;

        /// <summary>
        /// Just need to make sure every sub-class has a contructor accpets IRepositoryFactory parameter.
        /// </summary>
        /// <param name="session">An instance implements IRepositoryFactory.</param>
        public ServiceBase(IRepositoryFactory repositoryFactory, IDataValidator<T> entityValidator)
        {
            RepositoryFactory = repositoryFactory;
            EntityValidator = entityValidator;
        }

        /// <summary>
        /// Add new entity.
        /// </summary>
        /// <param name="entity">The new entity will be added into database.</param>
        /// <returns>The id of the new added entity.</returns>
        public virtual T Add(T entity)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                var addedEntity = Add(RepositoryFactory.CreateRepositoryBase<T>(), entity);
                RepositoryFactory.Commit();

                return addedEntity;
            }
        }

        /// <summary>
        /// Add new entity list.
        /// </summary>
        /// <param name="entities">The new entity list will be added into database.</param>
        /// <returns>The new added entity list.</returns>
        public virtual IList<T> Add(IEnumerable<T> entities)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                var repositoryBase = RepositoryFactory.CreateRepositoryBase<T>();

                foreach (var entity in entities)
                {
                    Add(repositoryBase, entity);
                }

                RepositoryFactory.Commit();
            }

            return entities.ToList();
        }

        // <summary>
        /// Get entity by id.
        /// </summary>
        /// <param name="id">The specified id.</param>
        /// <returns>The matched data.</returns>
        public virtual T Get(object id)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                return RepositoryFactory.CreateRepositoryBase<T>().Get(id);
            }
        }

        /// <summary>
        /// Query data by lambda expression.
        /// </summary>
        /// <param name="expression">The lambda expression that accepts TDataModel and return boolean.</param>
        /// <returns>An IList list of data.</returns>
        public virtual IList<T> Query(Expression<Func<T, bool>> expression)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                return RepositoryFactory.CreateRepositoryBase<T>().Query(expression).ToList();
            }
        }

        /// <summary>
        /// Query data by lambda expression.
        /// </summary>
        /// <typeparam name="TResult">The type of the value returned by selector.</typeparam>
        /// <param name="expression">The lambda expression that accepts TDataModel and return boolean.</param>
        /// <param name="selector">A transform function to apply to each element.</param>
        /// <returns>An IList list of data.</returns>
        public virtual IList<TResult> Query<TResult>(Expression<Func<T, bool>> expression, Expression<Func<T, TResult>> selector)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                return RepositoryFactory.CreateRepositoryBase<T>().Query(expression).Select<T, TResult>(selector).ToList();
            }
        }

        /// <summary>
        /// Get all data. You also can query the data later since the returned result is a IList collection.
        /// </summary>
        /// <returns>An IList collection of data.</returns>
        public virtual IList<T> GetAll()
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                return RepositoryFactory.CreateRepositoryBase<T>().GetAll().ToList();
            }
        }

        /// <summary>
        /// Get the data's count by using provided lambda expression.
        /// </summary>
        /// <param name="expression">The lambda expression that accepts T and return boolean.</param>
        /// <returns>The data's count.</returns>
        public int GetRowCount(Expression<Func<T, bool>> expression)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSession();
                return RepositoryFactory.CreateRepositoryBase<T>().GetRowCount(expression);
            }
        }

        /// <summary>
        /// Update existing entity.
        /// </summary>
        /// <param name="entity">The entity will be updated.</param>
        public virtual void Update(T entity)
        {
            EntityValidator.Validate(entity);

            if (EntityValidator.IsValid)
            {
                using (RepositoryFactory)
                {
                    RepositoryFactory.OpenSessionWithTransaction();
                    RepositoryFactory.CreateRepositoryBase<T>().Update(entity);
                    RepositoryFactory.Commit();
                }
            }
        }

        /// <summary>
        /// Update existing entity list.
        /// </summary>
        /// <param name="entities">The entity list will be updated.</param>
        public virtual void Update(IEnumerable<T> entities)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                var repositoryBase = RepositoryFactory.CreateRepositoryBase<T>();

                foreach (var entity in entities)
                {
                    EntityValidator.Validate(entity);

                    if (EntityValidator.IsValid)
                    {
                        repositoryBase.Update(entity);
                    }
                }

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Add or update entity.
        /// </summary>
        /// <param name="data">The entity will be updated if it is existing or added if it cannot be found.</param>
        public virtual void AddOrUpdate(T entity)
        {
            EntityValidator.Validate(entity);

            if (EntityValidator.IsValid)
            {
                using (RepositoryFactory)
                {
                    RepositoryFactory.OpenSessionWithTransaction();
                    RepositoryFactory.CreateRepositoryBase<T>().AddOrUpdate(entity);
                    RepositoryFactory.Commit();
                }
            }
        }

        /// <summary>
        /// Add or update entity list.
        /// </summary>
        /// <param name="entities">The entity list will be updated if it is existing or added if it cannot be found.</param>
        public virtual void AddOrUpdate(IEnumerable<T> entities)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                var repositoryBase = RepositoryFactory.CreateRepositoryBase<T>();

                foreach (var entity in entities)
                {
                    EntityValidator.Validate(entity);

                    if (EntityValidator.IsValid)
                    {
                        repositoryBase.AddOrUpdate(entity);
                    }
                }

                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Delete the specified entity.
        /// </summary>
        /// <param name="entity">The entity will be deleted from database.</param>
        public virtual void Delete(T entity)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                RepositoryFactory.CreateRepositoryBase<T>().Delete(entity);
                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Delete the specified entity list.
        /// </summary>
        /// <param name="entities">The entity list will be deleted from database.</param>
        public virtual void Delete(IEnumerable<T> entities)
        {
            using (RepositoryFactory)
            {
                RepositoryFactory.OpenSessionWithTransaction();
                RepositoryFactory.CreateRepositoryBase<T>().Delete(entities);
                RepositoryFactory.Commit();
            }
        }

        /// <summary>
        /// Add new entity.
        /// </summary>
        /// <param name="entity">The new entity will be added into database.</param>
        /// <returns>The id of the new added entity.</returns>
        protected virtual T Add(IRepository<T> repository, T entity)
        {
            EntityValidator.Validate(entity);

            if (EntityValidator.IsValid)
            {
                entity = repository.Add(entity);
            }
            else
            {
                entity = null;
            }

            return entity;
        }
    }
}
